#!/usr/bin/perl
#
# The Tungsten Sandbox
# (C) 2011-2014 Giuseppe Maxia, Continuent, Inc
# Released under the New BSD License
#

use strict;
use warnings;
use File::Basename;
use English '-no_match_vars';
use Data::Dumper;

{
package Sandbox;
# SBVERSION
our $VERSION = '3.1.07';
use English '-no_match_vars';
use File::Basename;

my $modulepath= get_real_path($PROGRAM_NAME);
eval qq(
use lib '$modulepath';
use CLI_Utils;
use base qw(CLI_Utils);
);

sub modulepath
{
    return $modulepath;
}

sub get_version
{
    return $VERSION;
}
sub get_credits
{
    return   "Tungsten Sandbox Manager\n"
           . "version $VERSION\n"
           . "(C) Continuent, Inc, 2012,2013,2014\n";
}

sub get_layout
{
    return   "[options]";
}

sub get_real_path
{
    my ($fname) = @_;
    my $real_path=dirname($fname);
    if ( -l $fname)
    {
        my $real_name = qx(readlink $fname);
        chomp $real_name;
        # print "# program_name: $real_name\n";
        $real_path=dirname($real_name);
    }
    return $real_path;
}

}  # end package Sandbox

package main;

use Data::Dumper;

my $cli = Sandbox->new(
    {
        program_name => 'tungsten-sandbox',
        main_option  => undef,
    }
);

$cli->add_option(
    nodes => {
        parse       => 'n|nodes=i',
        value       => 0,
        must_have   => 1,
        so          => 10,
        help        => ['Defines how many nodes will be in the sandbox'],
    }
);

$cli->add_option(
    mysql_version => {
        parse       => 'm|mysql-version=s',
        value       => undef,
        must_have   => 1,
        so          => 20,
        help        => ['which MySQL version will be used for the sandbox'],
    }
);

$cli->add_option(
    tungsten_base => {
        parse       => 't|tungsten-base=s',
        value       => "$ENV{HOME}/tsb3",
        must_have   => 0,
        so          => 30,
        help        => ['Where to install the sandbox'],
    }
);

$cli->add_option(
    staging_dir => {
        parse       => 'i|staging-dir=s',
        value       => undef,
        must_have   => 0,
        so          => 40,
        help        => ['Where the Tungsten tarball was expanded'],
    }
);

$cli->add_option(
    group_dir => {
        parse       => 'd|group-dir=s',
        value       => 'tsb',
        must_have   => 0,
        so          => 50,
        help        => ['Sandbox group directory name'],
    }
);

$cli->add_option(
    topology => {
        parse       => 'topology=s',
        value       => 'master_slave',
        must_have   => 1,
        allowed     => [qw(master_slave all_masters fanin star direct mongodb fileapplier sqlite)],
        so          => 60,
        help        => ['Which topology to deploy'],
    }
);

$cli->add_option(
    service => {
        parse       => 'service=s',
        value       => 'tsandbox',
        must_have   => 1,
        so          => 70,
        help        => ['How the service is named (in master/slave)'],
    }
);

$cli->add_option(
    mysql_port => {
        parse       => 'p|base-port=i',
        value       => 6000,
        must_have   => 1,
        so          => 80,
        help        => ['Base port for MySQL servers'],
    }
);

$cli->add_option(
    rmi_port => {
        parse       => 'r|rmi-port=i',
        value       => 10100,
        must_have   => 1,
        so          => 90,
        help        => ['Base port for RMI services'],
    }
);

$cli->add_option(
    thl_port => {
        parse       => 'l|thl-port=i',
        value       => 12100,
        must_have   => 1,
        so          => 100,
        help        => ['Base port for THL services'],
    }
);

$cli->add_option(
    defaults_options => {
        parse       => 'defaults-options=s',
        value       => undef,
        must_have   => 0,
        so          => 110,
        help        => ['Options that with be added to "tpm configure defaults" call'],
    }
);


$cli->add_option(
    master_options => {
        parse       => 'master-options=s',
        value       => undef,
        must_have   => 0,
        so          => 120,
        help        => ['Options that with be added to "tpm configure" call for a master service'],
    }
);

$cli->add_option(
    slave_options => {
        parse       => 'slave-options=s',
        value       => undef,
        must_have   => 0,
        so          => 130,
        help        => ['Options that with be added to "tpm configure" call for a slave service'],
    }
);

$cli->add_option(
    node_options => {
        parse       => 'node-options=s',
        value       => undef,
        must_have   => 0,
        so          => 135,
        help        => ['Options that with be added to "tpm configure" call for a given node',
                        'Format : "X:options", where "X" is the node number'],
    }
);

$cli->add_option(
    install_options => {
        parse       => 'install-options=s',
        value       => undef,
        must_have   => 0,
        so          => 140,
        help        => ['Options that with be added to "tpm install" calls for the current topology'],
    }
);

$cli->add_option(
    heterogenous_master => {
        parse       => 'hm|heterogenous-master|heterogeneous-master',
        value       => $ENV{HETEROGENOUS_MASTER} || $ENV{HETEROGENEOUS_MASTER} || 0,
        must_have   => 0,
        so          => 142,
        help        => ['Sets every master service with heterogeneous options'],
    }
);

$cli->add_option(
    batch_master => {
        parse       => 'bm|batch-master',
        value       => $ENV{BATCH_MASTER} || 0,
        must_have   => 0,
        so          => 143,
        help        => ['Sets every master service with batch options'],
    }
);


$cli->add_option(
    heterogenous_slave => {
        parse       => 'hs|heterogenous-slave|heterogeneous-slave',
        value       => $ENV{HETEROGENOUS_SLAVE} || $ENV{HETEROGENEOUS_SLAVE} || 0,
        must_have   => 0,
        so          => 144,
        help        => ['Sets every slave service with heterogeneous options'],
    }
);

$cli->add_option(
    batch_slave => {
        parse       => 'bs|batch-slave',
        value       => $ENV{BATCH_SLAVE} || 0,
        must_have   => 0,
        so          => 145,
        help        => ['Sets every slave service with batch options'],
    }
);


$cli->add_option(
    java_safe => {
        parse       => 'js|java-safe',
        value       => $ENV{JAVA_SAFE} || 0,
        must_have   => 0,
        so          => 147,
        help        => ['Uses character set UTF8 and GMT in the Java wrapper'],
    }
);

$cli->add_option(
    schema_change => {
        parse       => 'sc|schema-change',
        value       => $ENV{SCHEMA_CHANGE} || 0,
        must_have   => 0,
        so          => 148,
        help        => ['Install the schemachange filter on masters and monitorschemachange filter on slave services'],
    }
);


$cli->add_option(
    binlog_format => {
        parse       => 'binlog-format=s',
        value       => $ENV{BINLOG_FORMAT} || 'mixed',
        must_have   => 0,
        allowed     => [qw(STATEMENT MIXED ROW statement mixed row)],
        so          => 150,
        help        => ['Which binlog format shall we use'],
    }
);

$cli->add_option(
    rbr => {
        parse       => 'rbr|RBR',
        value       => 0,
        must_have   => 0,
        expands_to  => { binlog_format => 'row' }, 
        so          => 151,
        help        => ['Shortcut to --binlog-format=row'],
    }
);

$cli->add_option(
    time_zone => {
        parse       => 'time-zone=s',
        value       => $ENV{DBTIMEZONE} || '+00:00',
        must_have   => 0,
        so          => 155,
        help        => ['Which time zone the database should be set to'],
    }
);

$cli->add_option(
    multiple_time_zone => {
        parse       => 'multiple-time-zone',
        value       => undef,
        must_have   => 0,
        so          => 156,
        help        => ['Use different time zones for each node'],
    }
);

$cli->add_option(
    use_ini_files => {
        parse       => 'use-ini-files',
        value       => 0,
        must_have   => 0,
        so          => 160,
        help        => ['Uses .INI files instead of staging directory to run the installation'],
    }
);

$cli->add_option(
    parallel_replication => {
        parse       => 'pr|parallel-replication=n',
        value       => 0,
        must_have   => 0,
        expands_to  => { slave_options => '--svc-parallelization-type=disk --channels=%%value%%' },
        so          => 162,
        help        => ['Enables parallel replication in all nodes'],
    }
);

$cli->add_option(
    set_executable_prefix => {
        parse       => 'set-executable-prefix',
        value       => 0,
        must_have   => 0,
        so          => 165,
        help        => ['Set the executable prefix for each node'],
    }
);


$cli->add_option(
    dry_run => {
        parse       => 'dry-run',
        value       => 0,
        must_have   => 0,
        so          => 170,
        help        => ['Shows the installation commands, without doing anything'],
    }
);

$cli->add_option(
    debug => {
        parse       => 'debug',
        value       => $ENV{CLI_DEBUG} || $ENV{SB_DEBUG} || 0,
        must_have   => 0,
        so          => 180,
        help        => ['Shows debug information during the installation'],
    }
);

$cli->add_option(
    concurrent_sandboxes => {
        parse       => 'concurrent-sandboxes',
        value       => 0,
        must_have   => 0,
        so          => 190,
        help        => ['Defines different parameters for each topology'],
    }
);


$cli->add_option(
    use_ssl => {
        parse       => 'use-ssl',
        value       => undef,
        must_have   => 0,
        so          => 200,
        help        => ['Enables SSL connection for replication'],
    }
);

$cli->add_option(
    filter_in => {
        parse       => 'filter-in=s',
        value       => undef,
        must_have   => 0,
        expands_to  => { slave_options => '--svc-applier-filters=replicate --property=replicator.filter.replicate.do=%%value%%' },
        so          => 210,
        help        => ['Enable replicate filter to include one or more objects'],
    }
);


$cli->add_option(
    filter_out => {
        parse       => 'filter-out=s',
        value       => undef,
        must_have   => 0,
        expands_to  => { slave_options => '--svc-applier-filters=replicate --property=replicator.filter.replicate.ignore=%%value%%' },
        so          => 220,
        help        => ['Enable replicate filter to ignore one or more objects'],
    }
);


$cli->getoptions();
$cli->{options}{tungsten_sandbox_version} = $Sandbox::VERSION;
$cli->get_help() if $cli->{options}{help};
# print Dumper $cli->{options}; exit;


#if ( $cli->{options}{rbr} )
#{
#    if ($CLI_Utils::VERBOSE)
#    {
#        print "# option 'rbr' expanded to 'binlog-format=row'\n";
#    }
#    $cli->{options}{binlog_format} ='row';
#}

my %topology_defaults =(
    master_slave => { nodes => 2, db_port => 6100, rmi_port => 10200, thl_port => 12200, },
    all_masters  => { nodes => 2, db_port => 6200, rmi_port => 10300, thl_port => 12300, },
    star         => { nodes => 3, db_port => 6300, rmi_port => 10400, thl_port => 12400, },
    fanin        => { nodes => 3, db_port => 6400, rmi_port => 10500, thl_port => 12500, },
    direct       => { nodes => 2, db_port => 6500, rmi_port => 10600, thl_port => 12600, },
    mongodb      => { nodes => 3, db_port => 6600, rmi_port => 10700, thl_port => 12700, },
    fileapplier  => { nodes => 3, db_port => 6700, rmi_port => 10800, thl_port => 12800, },
    sqlite       => { nodes => 3, db_port => 6800, rmi_port => 10900, thl_port => 12900, },
);

my $topology = $cli->{options}{topology};
unless ($cli->{options}->{nodes})
{
    $cli->{options}->{nodes} = $topology_defaults{$topology}{nodes} ;
    if ($ENV{TEST_ALL_TOPOLOGIES})
    {
        $cli->{options}->{nodes} += 1 unless $topology =~ /^(?:mongodb|fileapplier|sqlite)$/;
    }
}

if ( $cli->{options}{concurrent_sandboxes})
{
    # 
    # Sets different paths and ports for each topology
    #
    $cli->{options}->{tungsten_base}    = "$ENV{HOME}/tsb/$topology" ;
    $cli->{options}->{group_dir}        = "tsb_$topology" ;
    $cli->{options}->{mysql_port}       = $topology_defaults{$topology}{db_port} ;
    $cli->{options}->{thl_port}         = $topology_defaults{$topology}{thl_port} ;
    $cli->{options}->{rmi_port}         = $topology_defaults{$topology}{rmi_port} ;
}

if ( $cli->{options}{set_executable_prefix})
{
   $ENV{SET_EXECUTABLE_PREFIX}=1;
}

if ( $cli->{options}{use_ssl})
{
    $ENV{USE_SSL}=1;
}

make_dynamic_options($cli->{options});

my $executable_path=Sandbox::get_real_path($PROGRAM_NAME);
my $curdir= $ENV{PWD};
chdir $executable_path;
$executable_path=qx(pwd);
chomp $executable_path;
chdir $curdir;

my $args = $cli->{options}->{nodes};

if (grep { $topology eq $_ } qw( master_slave direct fileapplier mongodb sqlite) )
{
    $args= "$cli->{options}{service} $cli->{options}{nodes}";
}
# print "<$excutable_path/sb_$topology $args>\n";
if ($cli->{options}{staging_dir})
{
    chdir $cli->{options}{staging_dir};
}

if ($cli->{options}{verbose})
{
    $ENV{VERBOSE}=1;
}
if ($cli->{options}{debug})
{
    $ENV{SBDEBUG}=1;
}

if ($cli->{options}{dry_run})
{
    $ENV{DRYRUN}=1;
}
$ENV{RUNNING_SANDBOX_WRAPPER}=1;

if ($cli->{options}{use_ini_files})
{
    my @existing_ini_files = glob ("$cli->{options}{tungsten_base}/tungsten-node?.ini");
    if (@existing_ini_files)
    {
        die "Found pre-existing .ini files : (@existing_ini_files)\n";
    }
    if (exists $ENV{USE_INI})
    {
        delete $ENV{USE_INI};
    }
    $ENV{MAKE_INI}=1;
    $ENV{DRYRUN}=1;
    if ($cli->{options}{debug})
    {
        system "$executable_path/sb_$topology $args";
    }
    else
    {
        system "$executable_path/sb_$topology $args > /dev/null 2>&1";
    }
    delete $ENV{MAKE_INI};
    delete $ENV{DRYRUN};
    $ENV{USE_INI}=1;
}
# print "$executable_path/sb_$topology $args\n";
exec "$executable_path/sb_$topology $args";

sub make_dynamic_options
{
    my ($options) = @_;
    my %options_to_vars =(
        tungsten_sandbox_version => 'TUNGSTEN_SANDBOX_VERSION',
        nodes                    => 'HOW_MANY_NODES',
        mysql_version            => 'MYSQL_VERSION',
        tungsten_base            => 'TUNGSTEN_SB',
        group_dir                => 'SB_DIRECTORY',
        mysql_port               => 'MYSQL_BASE_PORT',
        rmi_port                 => 'RMI_BASE_PORT',
        thl_port                 => 'THL_BASE_PORT',
        defaults_options         => 'MORE_DEFAULTS_OPTIONS',
        master_options           => 'MORE_MASTER_OPTIONS',
        slave_options            => 'MORE_SLAVE_OPTIONS',
        node_options             => 'MORE_NODE_OPTIONS',
        install_options          => 'MORE_TPM_INSTALL_OPTIONS',
        binlog_format            => 'BINLOG_FORMAT',
        time_zone                => 'DBTIMEZONE',
        multiple_time_zone       => 'MULTIPLE_TIMEZONE',
        use_ssl                  => 'USE_SSL',
    );
    if ( $options->{topology} eq 'sqlite')
    {
        $topology               = 'fileapplier';
        $options->{topology}    = 'fileapplier';
        $ENV{file_template}     = 'tosqlite';

        print "# Topology changed to <fileapplier> - But SQLite will run on top of it \n";
        my $mysql_cmd = CLI_Utils::which('mysql');
        unless ($mysql_cmd)
        {
            die "The SQLite applier requires MySQL command line client in the \$PATH\n";
        }
        if (($ENV{DB_USER} && $ENV{DB_PASSWORD}) or $ENV{MY_CNF})
        {
            # OK. We can run the sqlite applier
            my $source_directory    = Sandbox::get_real_path($PROGRAM_NAME);
            $source_directory       = "$source_directory/experimental";

            my $dest_directory      = $options->{staging_dir} || $ENV{PWD};
            $dest_directory         = "$dest_directory/tungsten-replicator/samples/scripts/batch/";
            die "can't access $dest_directory\n" unless -d $dest_directory;

            for my $file (('insert_to_sqlite.pl', 'tosqlite.js'))
            {
                if ( -f "$source_directory/$file")
                {
                    system("cp $source_directory/$file $dest_directory");
                }
                else
                {
                    die "can't find source file $source_directory/$file\n";
                }
            }
        }
        else
        {
            die "The SQLite applier requires that either the variable MY_CNF or both DB_USER and DB_PASSWORD be set\n";
        }
    }
    if ( $options->{topology} eq 'fileapplier')
    {
        $options->{batch_master} = 1;
        $options->{batch_slave} = 1;
        $options->{schema_change} = 1;
    }
    if ($options->{topology} eq 'mongodb')
    {
        $options->{heterogenous_master} = 1;
        $options->{heterogenous_slave} = 1;
    }
    $options->{master_options} = "" unless $options->{master_options};
    $options->{slave_options} = "" unless $options->{slave_options};
    $options->{defaults_options}  = "" unless $options->{defaults_options};
    if ($options->{heterogenous_master} or $options->{heterogenous_slave} or $options->{batch_master} or $options->{batch_slave} or $options->{java_safe})
    {
        $options->{defaults_options} .= " --java-file-encoding=UTF8 --java-user-timezone=GMT";
        # $options->{defaults_options} .= " --java-file-encoding=UTF8 ";
    }
    if ($options->{heterogenous_master})
    {
        $options->{master_options}   .= " --enable-heterogenous-master=true " ;
        $options->{binlog_format}     = "row";
    }

    if ($options->{heterogenous_slave})
    {
        $options->{slave_options} = "" unless $options->{slave_options};
        $options->{slave_options} .= " --enable-heterogenous-slave=true";
    }

    if ($options->{batch_master})
    {
        $options->{master_options}   .= " --enable-batch-master=true " ;
        $options->{binlog_format}     = "row";
    }

    if ($options->{batch_slave})
    {
        $options->{slave_options} = "" unless $options->{slave_options};
        $options->{slave_options} .= " --enable-batch-slave=true";
    }

    #if ($options->{parallel_replication})
    #{
    #    $options->{slave_options} = "" unless $options->{slave_options};
    #    $options->{slave_options} .= " --svc-parallelization-type=disk --channels=$options->{parallel_replication}";
    #}

    if ($options->{schema_change})
    {
        $options->{master_options} .= " --repl-svc-extractor-filters=schemachange" ;
        $options->{slave_options}  .= " --repl-svc-applier-filters=monitorschemachange --property=replicator.filter.monitorschemachange.notify=true";
    }
    if ($options->{defaults_options} && $CLI_Utils::VERBOSE)
    {
        print qq(# Setting MORE_DEFAULTS_OPTIONS:\n),
              qq(# "$options->{defaults_options}"\n);
    }
    if ($options->{master_options} && $CLI_Utils::VERBOSE)
    {
        print qq(# Setting MORE_MASTER_OPTIONS:\n),
              qq(# "$options->{master_options}"\n),
              qq(# Binlog-format="$options->{binlog_format}"\n);
    }
    if ($options->{slave_options} && $CLI_Utils::VERBOSE)
    {
        print qq(# Setting MORE_SLAVE_OPTIONS: \n),
              qq(# "$options->{slave_options}"\n);
    }

    my $dynamic_vars_file =  Sandbox::modulepath() . '/sb_dynamic_vars.sh';
    # print "$dynamic_vars_file\n";
    open my $DYNVARS, '>', $dynamic_vars_file
        or die "can't write to $dynamic_vars_file ($!)\n";

    for my $opt (keys %options_to_vars)
    {
        my $quote=q{};
        if (defined $options->{$opt} )
        {
            if ($options->{$opt} =~/\s/)
            {
                $quote=q{"};
            }
            print $DYNVARS qq(export $options_to_vars{$opt}=$quote$options->{$opt}$quote\n) ;
        }
    }
    close $DYNVARS;
}

__END__
